package at.chl.lution;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.log4j.Logger;

import at.chl.genetic.function.math.random.RandomMathDecisionNodeFactory;
import at.chl.genetic.function.request.IGeneticFunctionRequest;
import at.chl.genetic.function.result.IGeneticFunctionResult;
import at.chl.genetic.random.IRandomDecisionNodeFactory;
import at.chl.lution.creator.IGenerationCreator;
import at.chl.lution.creator.MathGenerationCreator;
import at.chl.lution.factory.BasicLutionistFactory;
import at.chl.lution.interpreter.IAccuracyInterpreter;
import at.chl.lution.interpreter.MathAccuracyInterpreter;
import at.chl.lution.list.AccuracySortedList;

public class BasicLutionRunner implements ILutionRunner{

	private Map<IGeneticFunctionRequest,IGeneticFunctionResult> expectionsMap;
	private IRandomDecisionNodeFactory nodeFactory;
	private Long populationSize=ILutionRunner.DEFAULT_POPULATION_SIZE;
	private Long roundsLimit=ILutionRunner.DEFAULT_ROUNDS_LIMIT;
	private Long surviverCount=ILutionRunner.DEFAULT_SURVIVER_COUNT;
	private Long sleepTime=ILutionRunner.DEFAULT_SLEEP_TIME;
	
	private IAccuracyInterpreter accuracyInterpreter;
	private IGenerationCreator generationCreator;
	
	private AccuracySortedList accuracySortedList;
	
	
	
	private List<ILutionist> population;
	
	private static Logger logger=Logger.getLogger(BasicLutionRunner.class);
	public BasicLutionRunner() {
		this(new RandomMathDecisionNodeFactory(),new MathAccuracyInterpreter(),new MathGenerationCreator());
	}
	public BasicLutionRunner(IRandomDecisionNodeFactory nodeFactory,IAccuracyInterpreter accuracyInterpreter,IGenerationCreator generationCreator) {
		this.nodeFactory=nodeFactory;
		this.accuracyInterpreter=accuracyInterpreter;
		this.generationCreator=generationCreator;
		
		
	}
	
	@Override
	public void runLution() {
		
		createPopulation();
		
		runLutionIntro();
		runLutionRounds();
		runLutionOutro();
		
		
	}
	
	
	private void runLutionOutro() {
		logger.warn("***running lution outro ***");
		
		logExpection();
		
		Collections.reverse(accuracySortedList);
		long position = accuracySortedList.size();
		
		for(Long id:accuracySortedList){
			if(position<surviverCount){
				ILutionist lutionist=getLutionist(id);
				logger.warn("Position "+position+": "+lutionist.getName()+" Accuracy: "+accuracySortedList.getValue(id));
				lutionist.getDecisionRootNode().print("");
			}
			position--;
		}
		
		
	}

	private void runLutionRounds() {
		logger.info("*** running lution rounds ***");
		
		
		
		for(int i=0;i<roundsLimit;i++){
			
			if(runLutionRound(i+1l)){
				break;
			}
		}
		
	}

	/**
	 * 
	 * @param round
	 * @return - true, if a perfect lutionist was found
	 */
	private boolean runLutionRound(Long round) {
		
		doSleep();
		logger.info("*** running Lution Round # "+round+" ***");
		
		//create grid
		Map<Long,List<Long>> accuracy=new HashMap<Long, List<Long>>();
		//test expections by lutionists and put results into grid
		for(ILutionist lutionist:population){
			accuracy.put(lutionist.getId(), getAccuracyList(lutionist));
		}
		
		//get average result by lutionist and sort them
		AccuracySortedList sortedList=new AccuracySortedList(accuracy);
		logSortedList(sortedList,accuracy);
		
		//if one lutionist is perfect stop method >> return;
		List<Long> perfects=sortedList.getPerfects();
		if(perfects!=null&&perfects.size()>0){
			logPerfects(perfects);
			accuracySortedList=sortedList;
			return true;
		}
		//remove all lutionists under surviverlimit (if 5, position 1-5 survive)
		removeFailedLutionists(sortedList);
		
		//create new lutionists by cloning/mutating survivernodes
		createNewGeneration(sortedList);
		accuracySortedList=sortedList;
		return false;
	}

	private void createNewGeneration(AccuracySortedList sortedList) {
		
		logger.info("*** creating new generation ***");
		
		long freeSpace=populationSize-population.size();
		
		
		for(int i=0;i<freeSpace;i++){
			
			if(i<surviverCount){
				Long id = sortedList.get(i);
				
				population.add(BasicLutionistFactory.createBasicLutionist(generationCreator.createNewGeneration(getLutionist(id).getDecisionRootNode(), nodeFactory)));
			}else{
				Long id = sortedList.get((int)(i%surviverCount));
			
				population.add(BasicLutionistFactory.createBasicLutionist(generationCreator.createNewGeneration(getLutionist(id).getDecisionRootNode(), nodeFactory)));
			}
		}
		
	}
	private void removeFailedLutionists(AccuracySortedList sortedList) {
		long position=1;
		for(Long id:sortedList){
			if(position>surviverCount){
				ILutionist lutionist=getLutionist(id);
				population.remove(lutionist);
			}
			position++;
		}
		
	}
	private void doSleep() {
		if(sleepTime>0){
			try {
				Thread.sleep(sleepTime);
			} catch (InterruptedException e) {
				//EXC
				e.printStackTrace();
			}
		}
		
	}
	private void logSortedList(AccuracySortedList sortedList,
			Map<Long, List<Long>> accuracy) {
		logger.info("*** accuracy sorted population ***");
		
		int position=1;
		
		for(Long id:sortedList){
			String surviver="";
			if(position<=surviverCount){
				surviver="*";
			}
			ILutionist lutionist=getLutionist(id);
			List<Long>	results=accuracy.get(id);
			logger.info(surviver+position+". "+lutionist.getName()+" >> Accuracy: "+sortedList.getValue(id)+" "+getDetailsString(results));
			
			if(surviver.equals("*")){
//				lutionist.getDecisionRootNode().print("");
			}
			position++;
		}
		
	}

	private String getDetailsString(List<Long> results) {
		StringBuffer buffer=new StringBuffer();
		buffer.append("( ");
		boolean isFirst=true;
		for(Long value:results){
			if(isFirst){
				buffer.append(value);
			}else{
				buffer.append(", "+value);
			}
			isFirst=false;
			
		}
		buffer.append(")");
		return buffer.toString();
	}

	private ILutionist getLutionist(Long id) {
		for(ILutionist lutionist:population){
			if(lutionist.getId()==id){
				return lutionist;
			}
		}
		return null;
	}

	private void logPerfects(List<Long> perfects) {
		logger.warn("*** developed perfect lutionist ***");
		
		for(Long id:perfects){
			ILutionist lutionist=getLutionist(id);
			logger.warn("perfect lutionists: "+lutionist.getName());
			lutionist.getDecisionRootNode().print("");
			
		}
		
	}

	private List<Long> getAccuracyList(ILutionist lutionist) {
		
		List<Long> accuracyResults=new ArrayList<Long>();
		
		for(Entry<IGeneticFunctionRequest, IGeneticFunctionResult> entry:expectionsMap.entrySet()){
			//interprete results and but the accuracyvalue into the list
			accuracyResults.add(accuracyInterpreter.interpretAccuracy(lutionist.evaluate(entry.getKey()), entry.getValue()));
			
		}
		
		return accuracyResults;
	}

	private void runLutionIntro() {
		logger.info("*** running lution intro ***");
		logger.info("PopulationSize: "+populationSize);
		logger.info("surviverCount: "+surviverCount);
		logger.info("roundsLimit: "+roundsLimit);
		
		
		logPopulation();
		logExpection();
	}

	public void createPopulation() {
		population=BasicLutionistFactory.createPopulation(populationSize,this.nodeFactory);
		
	}

	private void logExpection() {
		logger.warn("Expections("+expectionsMap.size()+"):");
		for(Entry<IGeneticFunctionRequest, IGeneticFunctionResult> entry:expectionsMap.entrySet()){
			logger.warn("Request: "+entry.getKey().toString()+" >> Result: "+entry.getValue().toString());
		}
		
	}

	private void logPopulation() {
		
		logger.info("Population("+population.size()+"): ");
		for(ILutionist lutionist:population){
			logger.info("lutionist: "+lutionist.getName());
		}
		
		
	}

	public void setPopulation(List<ILutionist> population) {
		this.population = population;
	}


	@Override
	public void setExpectionsMap(
			HashMap<IGeneticFunctionRequest, IGeneticFunctionResult> expectionsMap) {
		this.expectionsMap=expectionsMap;
		
	}
	@Override
	public void setNodeFactory(IRandomDecisionNodeFactory nodeFactory) {
		this.nodeFactory=nodeFactory;
		
	}
	@Override
	public void setPopulationSize(Long populationSize) {
		this.populationSize=populationSize;
		
	}
	@Override
	public void setRoundsLimit(Long roundsLimit) {
		this.roundsLimit=roundsLimit;
		
	}
	@Override
	public void setSurviverCount(Long surviverCount) {
		this.surviverCount=surviverCount;
		
	}
	public void setAccuracyInterpreter(IAccuracyInterpreter accuracyInterpreter) {
		this.accuracyInterpreter = accuracyInterpreter;
	}
	public void setSleepTime(Long sleepTime) {
		this.sleepTime = sleepTime;
	}
	

}
